package com.zhiyun.mojos;

import com.google.common.collect.Lists;
import com.thoughtworks.qdox.JavaProjectBuilder;
import com.thoughtworks.qdox.model.*;
import com.thoughtworks.qdox.model.expression.AnnotationValue;
import com.thoughtworks.qdox.model.impl.DefaultJavaParameterizedType;
import com.zhiyun.contants.TagConstants;
import com.zhiyun.pos.ClassInfo;
import com.zhiyun.pos.MethodInfo;
import com.zhiyun.pos.FieldInfo;
import com.zhiyun.utils.*;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.beetl.core.Template;

import java.io.File;
import java.util.*;
import java.util.stream.Collectors;

/**
 * markdown mojo
 *
 * @author kunkun
 */
@Mojo(name = "markdown")
public class MarkdownMojo extends AbstractMojo {

    @Parameter(defaultValue = "${outputPath}")
    private String outputPath;

    @Parameter(defaultValue = "${packages}")
    private String packages;

    @Parameter(defaultValue = "${project}", readonly = true, required = true)
    private MavenProject project;


    @Override
    public void execute() throws MojoExecutionException {
        List<JavaClass> allClasses = CacheUtil.get(CacheUtil.ALL_CLASSES);
        if (allClasses != null && allClasses.size() > 0) {
            getLog().info("已经获取了全部class，故跳过此module：" + project.getName());
        } else {
            long startMill = System.currentTimeMillis();
            getLog().info("开始构建 markdown 文档");
            allClasses = JavaClassUtil.getAllClasses(project);
            CacheUtil.put(CacheUtil.ALL_CLASSES, allClasses);
            createMD(allClasses);
            long endMill = System.currentTimeMillis();
            getLog().info("构建 markdown 文档完毕, 耗时 " + (endMill - startMill) / 1000 + "s");
        }
    }

    /**
     * 生成markdown
     * @param classes
     * @throws MojoExecutionException
     */
    private void createMD(List<JavaClass> classes) throws MojoExecutionException {

        if(StringUtil.isEmpty(packages)){
            throw new RuntimeException("请指定service所在的包路径后，再生成doc文档");
        }

        String[] split = packages.split(",");

        for (JavaClass javaClass : classes) {
            String packageName = javaClass.getPackageName();
            if (!inPackages(packageName, split)) {
                continue;
            }
            if (!javaClass.isInterface()) {
                continue;
            }
            if(!haveServiceAnnotation(javaClass)){
                continue;
            }

            // 1. 获取类基本信息
            ClassInfo classInfo = getClassInfo(javaClass);

            // 2. 获取类方法信息
            List<MethodInfo> methodInfos = getMethodInfos(javaClass);

            // 3. 数据绑定及渲染
            Template template = BeetlTemplateUtil.getByName("markdown.btl");
            template.binding("classInfo", classInfo);
            template.binding("methodInfos", methodInfos);
            String strTime = DateTimeUtil.long2Str(System.currentTimeMillis(), DateTimeUtil.DATE_FORMAT_SECOND);
            template.binding("createTime", strTime);
            FileUtil.nioWriteFile(template.render(), outputPath + File.separator + javaClass.getName() + ".md");
        }
    }

    private boolean inPackages(String packageName, String[] split) {
        for(String item : split){
            if(packageName.startsWith(item)){
                return true;
            }
        }
        return false;
    }

    private boolean haveServiceAnnotation(JavaClass javaClass) {
        List<JavaAnnotation> annotations = javaClass.getAnnotations();
        for(JavaAnnotation annotation : annotations){
            JavaClass type = annotation.getType();
            String qualifiedName = type.getFullyQualifiedName();
            if(qualifiedName.equals("com.jkys.phobos.annotation.Service")){
                return true;
            }
        }
        return false;
    }

    private List<MethodInfo> getMethodInfos(JavaClass javaClass) {
        List<JavaMethod> methods = javaClass.getMethods();
        return methods.stream().map(s -> {
            String name = s.getName();
            List<ClassInfo> requestClass = getRequestClass(s);
            ClassInfo responseClass = getResponseClass(s);
            MethodInfo methodInfo = new MethodInfo();
            methodInfo.setComment(getDefault(s.getComment(), "请添加方法注释"));
            methodInfo.setName(name);
            methodInfo.setRequestClasses(requestClass);
            methodInfo.setResponseClass(responseClass);
            return methodInfo;
        }).collect(Collectors.toList());
    }

    /**
     * 获取响应参数
     *
     * @param s
     * @return
     */
    private ClassInfo getResponseClass(JavaMethod s) {
        Map<String, JavaClass> map = CacheUtil.toMap(CacheUtil.ALL_CLASSES);
        ClassInfo responseClass = new ClassInfo();
        JavaClass returnClass = s.getReturns();
        String name = returnClass.getName();
        String fullyQualifiedName = returnClass.getFullyQualifiedName();
        if (name.equals("void")) {
            responseClass.setName("空");
        }

        responseClass.setName(name);
        responseClass.setFullName(fullyQualifiedName);

        List<FieldInfo> fieldInfos = toFieldInfo(returnClass);
        responseClass.setFieldInfoList(fieldInfos);

        // 泛型
        List<JavaType> actualTypeArguments = ((DefaultJavaParameterizedType) returnClass).getActualTypeArguments();
        if (actualTypeArguments != null && actualTypeArguments.size() > 0) {
            List<ClassInfo> genericClasses = new ArrayList<>();
            for (JavaType javaType : actualTypeArguments) {
                String qualifiedName = javaType.getFullyQualifiedName();
                JavaClass javaClass = map.get(qualifiedName);
                ClassInfo gClassInfo = new ClassInfo();
                if(javaClass != null){
                    String gFullName = javaClass.getFullyQualifiedName();
                    String gName = javaClass.getName();
                    String gComment = javaClass.getComment();
                    gClassInfo.setName(gName);
                    gClassInfo.setFullName(gFullName);
                    gClassInfo.setComment(gComment);
                    List<FieldInfo> fielTypeFields = toFieldInfo(javaClass);
                    gClassInfo.setFieldInfoList(fielTypeFields);
                }else {
                    gClassInfo.setName(javaType.getBinaryName());
                    gClassInfo.setFullName(javaType.getFullyQualifiedName());
                }
                genericClasses.add(gClassInfo);
            }
            responseClass.setGenericClasses(genericClasses);
        }


        return responseClass;
    }

    private Set<String> getGeneSet(JavaClass javaClass) {
        List<JavaTypeVariable<JavaGenericDeclaration>> typeParameters = javaClass.getTypeParameters();
        return typeParameters.stream().map(z -> z.getName()).collect(Collectors.toSet());
    }

    private boolean notJdkType(String fullyQualifiedName) {
        return !fullyQualifiedName.startsWith("java.lang")
                && !fullyQualifiedName.startsWith("java.util")
                && !fullyQualifiedName.startsWith("java.time");
    }

    /**
     * 获取请求参数
     *
     * @param s
     * @return
     */
    private List<ClassInfo> getRequestClass(JavaMethod s) {
        List<ClassInfo> classInfos = new ArrayList<>();
        List<JavaParameter> parameters = s.getParameters();
        if(parameters.size() == 0){
            ClassInfo classInfo = new ClassInfo();
            classInfo.setFullName("无");
            classInfo.setName("无");
            classInfo.setComment("无");
            classInfos.add(classInfo);
            return classInfos;
        }
        List<DocletTag> paramComments = s.getTagsByName("param");
        Map<String, String> commentMap = new HashMap<>();
        for (DocletTag docletTag : paramComments) {
            List<String> tagParams = docletTag.getParameters();
            if (tagParams.size() > 1) {
                commentMap.put(tagParams.get(0), tagParams.get(1));
            }
        }

        for (JavaParameter javaParameter : parameters) {
            String name = javaParameter.getName();
            String fullyQualifiedName = javaParameter.getFullyQualifiedName();
            ClassInfo classInfo = new ClassInfo();
            classInfo.setName(name);
            classInfo.setFullName(fullyQualifiedName);
            classInfo.setComment(commentMap.get(name));
            JavaClass declaringClass = javaParameter.getJavaClass();
            List<FieldInfo> fieldInfos = toFieldInfo(declaringClass);
            classInfo.setFieldInfoList(fieldInfos);
            classInfos.add(classInfo);
        }
        return classInfos;
    }

    /**
     * 获取类中的所有字段
     * @param javaClass
     * @return
     */
    private List<FieldInfo> toFieldInfo(JavaClass javaClass) {
        String qualifiedName = javaClass.getFullyQualifiedName();
        if(!notJdkType(qualifiedName)){
            return Collections.EMPTY_LIST;
        }
        Set<String> geneSet = getGeneSet(javaClass);
        List<JavaField> fields = javaClass.getFields();
        JavaClass superClass = javaClass.getSuperJavaClass();
        while (superClass != null && !superClass.getFullyQualifiedName().equals("java.lang.Object")) {
            fields.addAll(superClass.getFields());
            geneSet.addAll(getGeneSet(superClass));
            superClass = superClass.getSuperJavaClass();
        }

        List<FieldInfo> collect = new ArrayList<>();
        Set<String> fieldNames = new HashSet<>();
        for (JavaField x : fields) {
            String fName = x.getName();
            if (fieldNames.contains(fName)) {
                continue;
            }
            String comment = x.getComment();
            if(comment == null){
                List<JavaAnnotation> annotations = x.getAnnotations();
                for(JavaAnnotation ja : annotations){
                    if(!ja.getType().getSimpleName().equals("io.swagger.annotations.ApiModelProperty")){
                        continue;
                    }
                    AnnotationValue valueA = ja.getProperty("value");
                    comment = (String) valueA.getParameterValue();
                    break;
                }
            }
            FieldInfo fieldInfo = new FieldInfo(fName, comment == null ? "无" : comment.replaceAll("\"","").replaceAll("\n","/"), geneSet.contains(x.getType().getName()) ? "泛型:" + x.getType().getName() : x.getType().getName());
            collect.add(fieldInfo);
            fieldNames.add(fName);
        }
        return collect;
    }

    private ClassInfo getClassInfo(JavaClass javaClass) {
        ClassInfo classInfo = new ClassInfo();
        classInfo.setComment(getDefault(javaClass.getComment(), "请添加类注释"));
        classInfo.setName(javaClass.getName());
        String authorName = JavaClassUtil.getClassTagsValue(javaClass, TagConstants.AUTHOR, Boolean.FALSE);
        String since = JavaClassUtil.getClassTagsValue(javaClass, TagConstants.SINCE, Boolean.FALSE);
        String version = JavaClassUtil.getClassTagsValue(javaClass, TagConstants.VERSION, Boolean.FALSE);
        String date = JavaClassUtil.getClassTagsValue(javaClass, TagConstants.DATE, Boolean.FALSE);
        classInfo.setAuthor(getDefault(authorName, "请添加作者"));
        classInfo.setSince(since);
        classInfo.setVersion(version);
        classInfo.setDate(date);
        classInfo.setFullName(javaClass.getFullyQualifiedName());
        return classInfo;
    }

    private String getDefault(String comment, String desc) {
        if (comment == null || comment.trim().length() == 0) {
            return desc;
        }
        return comment;
    }


}
